<?php
/**
 * VerifyCodeServ.php
 *
 * @copyright   2023 thingsworld.com.cn - All Rights Reserved
 * @link        http://thingsworld.com.cn/
 * @author      stiffer.chen <chenl@thingsworld.com.cn>
 * @created     2023-01-30 17:21:07
 * @modified    2023-01-30 17:21:07
 *
 */

namespace App\Services;

use App\Library\Logger;
use App\Repositories\Customer\UserRepo;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;

class VerifyCodeServ
{
    const CODE_PREFIX = 'verify_code_';
    const CODE_KEY = 'input_customer_telephone'; // 客户登录缓存key
    const EXPIRED_MINUTE = 5;
    const EXPIRED_TTL = self::EXPIRED_MINUTE * 60 * 1000;
    const REQUEST_LIMIT_SECOND = 1;

    private $account = null;
    private $log;

    public function __construct($account)
    {
        $this->account = $account;
        $this->verifyCode();
        $this->log = new Logger('verify_code');
    }

    /**
     * 生成验证码
     * 1、检查验证码是否存在于缓存中、是否已过期、如果存在且未过期则直接发送
     * 2、验证码请求间隔时间不低于一分钟，低于一分钟则抛异常
     * 3、验证码不存在或已过期：验证账号是否存在，账号存在则生成、存入缓存、发送
     * 4、验证码内容: 验证码 + 生成时间 + 联系人信息
     *
     */
    public function generate()
    {
        $verifyCodeKey = $this->generateKey();
        $verifyCodeCache = Cache::get($verifyCodeKey);
        if (!empty($verifyCodeCache)) {
            $verifyCodeCreatedAt = $verifyCodeCache['created_at'];
            if (Carbon::parse($verifyCodeCreatedAt)->addMinutes(self::REQUEST_LIMIT_SECOND) > Carbon::now()->toDateTimeString()) {
                //throw new \Exception(t('verify_code.request_often'));
            }
        }
        $code = $this->generateCode();
        try {
            $user = UserRepo::fetchByAccount($this->account);
        } catch (\Exception $t) {
            $this->log->info("生成验证码时， 客户信息为空, 客户直接赋空值");
            $user = null;
        }
        $createdAt = Carbon::now()->toDateTimeString();

        $verifyCodeData = [
            'code' => $code,
            'created_at' => $createdAt,
            'customer_user' => $user
        ];

        $template = "【物控联动】您的验证码为 {$code} ，该验证码5分钟内有效，请勿泄露于他人。";
        $this->log->info($template);
        $this->send($this->account, $code, $template);

        Cache::put($verifyCodeKey, $verifyCodeData, self::EXPIRED_TTL);
    }

    public function validate($code)
    {
        $key = $this->generateKey();
        $verifyCode = Cache::get($key);
        if (empty($verifyCode)) {
            throw new \Exception(t('verify_code.error_exist'));
        }
        $cacheCode = $verifyCode['code'];
        if ($cacheCode != $code) {
            throw new \Exception(t('verify_code.error_code'));
        }
        $now = Carbon::now()->toDateTimeString();
        $codeCreatedAt = $verifyCode['created_at'];
        $expiredTime = Carbon::parse($codeCreatedAt)->addMinutes(self::EXPIRED_MINUTE)->toDateTimeString();
        if ($expiredTime <= $now) {
            throw new \Exception(t('verify_code.error_expired'));
        }
        $customerUser = $verifyCode['customer_user'];
        return $customerUser;
    }

    public function validateCode($code)
    {
        if (!$code) {
            throw new \Exception('验证码为空');
        }
        $key = $this->generateKey();
        $verifyCodeCache = Cache::get($key);
        if (empty($verifyCodeCache)) {
            throw new \Exception('验证码不存在');
        }
        $cacheCode = $verifyCodeCache['code'] ?? '';
        if ($cacheCode != $code) {
            throw new \Exception('验证码不存在');
        }
        $createdAt = $verifyCodeCache['created_at'];
        $expiredAt = Carbon::parse($createdAt)->addMinutes(5)->toDateTimeString();
        $now = Carbon::now()->toDateTimeString();
        if ($expiredAt <= $now) {
            throw new \Exception('验证码已过期');
        }
    }

    /**
     * 发送验证码
     * @param string $account 账号(邮箱|电话号码)
     * @param string $template 模板
     */
    public function send(string $account, $code, string $template)
    {
        $this->log->info("发送验证码");
        $this->log->info("账号: {$account}");
        $this->log->info("发送内容: {$template}");

        if (verify_telephone($account)) {
            $this->sendSms($account, $code, $template);
        }

        if (verify_email($account)) {
            $this->sendEmail($account, $template);
        }
    }

    public function sendSms(string $account, string $code, string $template)
    {
        $this->log->info("发送短信验证码， 账号: {$account}");
        try {
            $params = [
                'code' => $code
            ];
            (new SmsServ($account))->send('SMS_463645433', json_encode($params));
        } catch (\Exception $e) {
            throw new \Exception('验证码发送失败: ' . $e->getMessage());
        }
    }

    public function sendEmail(string $account, string $template)
    {
        $this->log->info("发送邮件验证码， 账号: {$account}");
    }

    /**
     * 验证账号格式是否正确
     *
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    private function verifyCode()
    {
        $account = $this->account;
        if (!verify_email($account) && !verify_telephone($account)) {
            throw new \Exception(t('user.invalid_account_format'));
        }
    }

    private function generateCode()
    {
        return rand(10000, 99999);
    }

    private function generateKey()
    {
        return self::CODE_PREFIX . $this->account;;
    }
}